﻿//------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// Description: Validation Rule to validate the XPaths and their value as 
//              specified in the input file 
//
//------------------------------------------------------------------------------
using System;
using System.IO;
using System.ComponentModel;
using Microsoft.VisualStudio.TestTools.WebTesting;
using System.Xml;
using HtmlAgilityPack;

namespace WMIS.Test.Common.VSTS.WebTest
{
    [DisplayName("Multiple XPaths and Values")]
    [Description("Validates the XPaths and their value as specified in the input file.")]
    public class ValidateMultipleXPathsValues : ValidationRule
    {
        // The File Path
        private string filePath;

        [DisplayName("Required File Path")]
        [Description("The absolute file path containing XPaths and Values. The expected file format is: \n<XPath><Delimiter><Value>\n<XPath><Delimiter><Value>\n<XPath><Delimiter><Value>")]
        public string RequiredFilePath
        {
            get { return filePath; }
            set { filePath = value; }
        }

        // The delimiter
        private char delimiter;

        [DisplayName("Delimiter")]
        [Description("The delimiter that separates the XPath and its value. The character chosen as the delimiter must not be present in the XPath and value.")]
        public char SpecifiedDelimiter
        {
            get { return delimiter; }
            set { delimiter = value; }
        }

        //  Should the Case be ignored while comparing the InnerText
        private bool ignoreCaseValue = false;

        [DisplayName("Ignore Case")]
        [Description("Whether case should be ignored or not while comparing the XPath Value.")]
        [DefaultValue(false)]
        public bool IgnoreCase
        {
            get { return ignoreCaseValue; }
            set { ignoreCaseValue = value; }
        }
    
        // Validate is called with the test case Context and the request context.
        // These allow the rule to examine both the request and the response.
        //---------------------------------------------------------------------
        public override void Validate(object sender, ValidationEventArgs e)
        {
            bool validated = false, overallValidation = true;
            bool nodeFound = false;
            bool tokensFound = false;
            string xPath = String.Empty,
                   xPathValue = String.Empty,
                   errorString = String.Empty;
            string[] tokens;

            try
            {
                if (String.Equals("", delimiter.ToString(), StringComparison.InvariantCultureIgnoreCase))
                {
                    throw new NullReferenceException("The delimiter cannot be empty");
                }
                                
                using (FileStream fs = File.Open(RequiredFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    string[] lines = File.ReadAllLines(RequiredFilePath);

                    foreach (string line in lines)
                    {
                        validated = false;
                        nodeFound = false;
                        tokensFound = false;

                        tokens = line.Split(delimiter);

                        if (tokens == null)
                        {
                            throw new NullReferenceException("The tokens obtained using the delimiter were null");
                        }
                        
                        if (tokens.Length > 2 || tokens.Length < 2)
                        {
                            tokensFound = false;
                            xPath = String.Empty;
                            xPathValue = String.Empty;
                        }
                        else
                        {
                            tokensFound = true;
                            xPath = tokens[0];
                            xPathValue = tokens[1];
                        }

                        if (e.Response.IsXml.Equals(true) && xPath.Length != 0)
                        {
                            //  It's an XML response
                            XmlNodeList xmlNodeList = e.Response.XmlDocument.SelectNodes(xPath);
                            
                            if (xmlNodeList.Count > 0)
                            {
                                nodeFound = true;
                            }

                            foreach (XmlNode xmlNode in xmlNodeList)
                            {
                                if (xPathValue.Length == 0 || String.Equals(xPathValue, xmlNode.InnerText.Trim(), (ignoreCaseValue == true) ? StringComparison.InvariantCultureIgnoreCase : StringComparison.InvariantCulture))
                                {
                                    validated = true;
                                    break;
                                }
                            }
                        }
                        else if (e.Response.IsHtml.Equals(true) && xPath.Length != 0)
                        {
                            //It's an HTML response
                            HtmlAgilityPack.HtmlDocument mainDocument = new HtmlAgilityPack.HtmlDocument();
                            mainDocument.LoadHtml(e.Response.BodyString);

                            HtmlNode mainNode = mainDocument.DocumentNode;

                            if (mainNode.SelectNodes(xPath) != null)
                            {
                                foreach (HtmlNode htmlNode in mainNode.SelectNodes(xPath))
                                {
                                    nodeFound = true;

                                    if (xPathValue.Length == 0 || String.Equals(xPathValue, htmlNode.InnerText.Trim(), (ignoreCaseValue == true) ? StringComparison.InvariantCultureIgnoreCase : StringComparison.InvariantCulture))
                                    {
                                        validated = true;
                                        break;
                                    }
                                    else
                                    {
                                        //  User would have specified an attribute in the XPath, let's check if the value matches with the attribute's value
                                        foreach (HtmlAgilityPack.HtmlAttribute htmlAttribute in htmlNode.Attributes)
                                        {
                                            if (String.Equals(xPathValue, htmlAttribute.Value.ToString(), (ignoreCaseValue == true) ? StringComparison.InvariantCultureIgnoreCase : StringComparison.InvariantCulture))
                                            {
                                                validated = true;
                                                break;
                                            }
                                        }

                                        //  We don't want to continue with the outer for loop if we've found a match
                                        if (validated == true)
                                        {
                                            break;
                                        }
                                    }
                                }
                            }
                        }

                        // If the validation fails, set the error text that the user sees
                        if (!validated)
                        {
                            overallValidation = validated;

                            if (tokensFound == false)
                            {
                                if (tokens.Length < 2)
                                {
                                    errorString = String.Concat(errorString, String.Format("\r\nThe delimiter '{0}' is not present on line '{1}'", delimiter.ToString(), line.ToString()));

 
                                }
                                else if (tokens.Length > 2)
                                {
                                    errorString = String.Concat(errorString, String.Format("\r\nThe delimiter '{0}' is present more than once on line '{1}'", delimiter.ToString(), line.ToString()));

                                }
                                
                            }
                            else if (nodeFound == false)
                            {
                                errorString = String.Concat(errorString, String.Format("\r\nThe specified XPath '{0}' on line '{1}' was not found", xPath.ToString(), line.ToString()));
                            }
                            else
                            {
                                errorString = String.Concat(errorString, String.Format("\r\nThe specified value '{0}' was not found for the XPath '{1}' on line '{2}' ", xPathValue.ToString(), xPath.ToString(), line.ToString()));
                            }
                        }
                    }

                    e.IsValid = overallValidation;

                    e.Message = String.Copy(errorString);
                }
            }
            catch (Exception ex)
            {
                e.IsValid = validated;
                e.Message = String.Format("An exception occurred. Input filename: '{0}', XPath: '{1}' and Value: '{2}'. Stack Dump: {3}", filePath, xPath, xPathValue, ex);

            }            
        }
        
    }
}